package jfprincesstests.utilityfuncprotocol;

import java.util.*;

import jfuzzyprincepless.zarchive.algorithm.structure.ChainCommand;
import jfuzzyprincepless.zarchive.algorithm.structure.ICommandConstruction;
import jfuzzyprincepless.zarchive.algorithm.structure.IMultiConstruction;
import jfuzzyprincepless.zarchive.algorithm.structure.forcnstr.*;
import jfuzzyprincepless.zarchive.algorithm.structure.forcnstr.INegotiationFor;
import jfuzzyprincepless.zarchive.algorithm.structure.forcnstr.fuzzyfor.*;
import jfuzzyprincepless.zarchive.agents.BaseUtilityFuncAgent;
import jfuzzyprincepless.zarchive.agents.IUtilityFuncNegotiationAgent;
import jfuzzyprincepless.zarchive.negotiation.protocols.utilityfuncprotocol.UtilityFunctionBasedProtocol;
import jfuzzyprincepless.zarchive.negotiation.protocols.utilityfuncprotocol.IUtilityFuncNegotiation;
import jfuzzyprincepless.zarchive.negotiation.protocols.utilityfuncprotocol.UtilityFuncNegotiation;
import jfuzzyprincepless.zarchive.negotiation.protocols.propositionnetwork.DefaultNegotiation__Q;
import jfuzzyprincepless.zarchive.negotiation.protocols.propositionnetwork.ExecuteForNegotiation;
import jfuzzyprincepless.zarchive.negotiation.ZArchNegotiationUtils;
import jfuzzyprincepless.negotiation.DefaultNegotiationContext;
import jfuzzyprincepless.zarchive.negotiation.IAgentsExporter;
import jfuzzyprincepless.zarchive.negotiation.services.IUtilityInfoService;
import jfuzzyprincepless.zarchive.negotiation.services.IExecuteForService;
import jfuzzyprincepless.negotiation.IExecutionContext;
import jfuzzyprincepless.negotiation.INegotiation;
import jfuzzyprincepless.negotiation.INegotiationContext;
import jfuzzyprincepless.agents.*;
import laboratoryQ.junit.ignore.patch.BaseTestCase;
import jfprincesstests.fakes.*;
import laboratoryQ.computation.BaseUtils;

public class TestUtilityFuncBasedNegotiationProtocol
	extends BaseTestCase
{
	public void testCreateNegotiation() {
		UtilityFunctionBasedProtocol protocol =
			new UtilityFunctionBasedProtocol(
				ZArchNegotiationUtils.newDefaultNegotiationProtocol()
			);
		INegotiationFor<Integer> forLoop =
			new NegotiationFuzzyFor<Integer>();
		INegotiation negotiation =
			protocol.createNegotiation(forLoop, null);
		assertNotNull(negotiation);
		assertIsInstance(
			negotiation,
			IUtilityFuncNegotiation.class
		);
	}

	public void testDefaultUtilityFuncNegotiation_on_100$100e_share() {
		StringBuilder sb = new StringBuilder();

		double[] shared100$ = new double[] { 100.0 };
		double[] shared100E = new double[] { 100.0 };
		Player100$ american = new Player100$(
			"american",
			shared100$, 5,
			shared100E, 1,
			sb
		);
		Player100$ european = new Player100$(
			"european",
			shared100$, 1,
			shared100E, 5,
			sb
		);
		// DECIDE: [probable trash] FOR- construction is an agent that runs all body commands' negotiations

		ChainCommand cmdBlock = new ChainCommand(
			american,
			european
		);
		cmdBlock.extractAgentsFromConstructions();
		IUtilityFuncNegotiation negotiation =
			new UtilityFuncNegotiation(
				new DefaultNegotiation__Q(cmdBlock, cmdBlock)
			);
		assertNotNull(negotiation);

		IExecutionContext context = negotiation.negotiate();
		negotiation.execute(context);

		assertEquals(
			"name: american $75.0 E30.0;" +
			"name: european $25.0 E70.0;",
			sb.toString()
		);
	}

	private static class Player100$
		extends BaseUtilityFuncAgent
		implements ICommandConstruction
	{
		private double[] shared100$;
		private double[] shared100E;
		private double veight$;
		private double veightE;

		private double myMoney$ = 0.0;
		private double myMoneyE = 0.0;

		private String name;
		private double step = 5.0;
		private boolean wantAll = false;
		private StringBuilder sb;

		private Player100$(
			String name,
			double[] shared100$,
			double veight$,
			double[] shared100E,
			double veightE,
			StringBuilder sb
		) {
			this.name = name;
			this.shared100$ = shared100$;
			this.shared100E = shared100E;
			this.veight$ = veight$;
			this.veightE = veightE;
			this.sb = sb;
		}

		@Override
		public void execute(IExecutionContext context) {
			writeToBuf(sb);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			IUtilityInfoService utilServ =
				context.getService(IUtilityInfoService.class);
			double myPart$ = 0.0;
			double myPartE = 0.0;
			if (myMoney$ + myMoneyE == 0.0) {
				double myPart = 50.0;
				myPart$ = myPart;
				myPartE = myPart;
			} else if (wantAll) {
				myPart$ = shared100$[0];
				myPartE = shared100E[0];
				wantAll = false;
			} else if (!utilServ.getCriticalParams().isEmpty()) {
				int criticalIndex =
					(Integer)utilServ.getCriticalParams().get(0);
				if (criticalIndex == myCriticalIndex()) {
					wantAll = true;
					return true;
				}
				if (criticalIndex == 0) {
					myPart$ = -step;
				} else {
					myPartE = -step;
				}
				myPart$ = -Math.min(-myPart$, myMoney$);
				myPartE = -Math.min(-myPartE, myMoneyE);
			}
			shared100$[0] -= myPart$;
			myMoney$ += myPart$;
			shared100E[0] -= myPartE;
			myMoneyE += myPartE;
			return false;
		}

		@Override
		public double computeUtility(
			INegotiationContext context
		) {
			return myMoney$ * veight$ + myMoneyE * veightE;
		}

		@Override
		public void addCriticalParams(List<Object> criticalParams) {
			int criticalIndex = myCriticalIndex();
			criticalParams.add(criticalIndex);
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			writeToBuf(sb);
			return sb.toString();
		}

		private int myCriticalIndex() {
			int criticalIndex = veight$ > veightE ? 0 : 1;
			return criticalIndex;
		}

		private void writeToBuf(StringBuilder sb) {
			sb.append("name: ").append(name)
				.append(" $").append(myMoney$)
				.append(" E").append(myMoneyE)
				.append(";");
		}
	}

	public void testUtilityFunctionAgent() {
		StringBuilder sb = new StringBuilder();

		INegotiatingAgent player = new Player100$(
			"p",
			new double[] { 100.0 },
			1,
			new double[] { 100.0 },
			2,
			sb
		);
		assertIsInstance(player, IUtilityFuncNegotiationAgent.class);
		IUtilityFuncNegotiationAgent agent =
			(IUtilityFuncNegotiationAgent) player;
		assertNotNull(agent);

		UtilityFuncNegotiation negotiation =
			new UtilityFuncNegotiation(
				new DefaultNegotiation__Q(
					IMultiConstruction.EMPTY,
					IAgentsExporter.EMPTY_EXPORTER
				)
			);
		INegotiationContext context = new DefaultNegotiationContext();
		assertEquals(0.0, agent.computeUtility(context));

		context.registerService(negotiation);

		boolean wantToNegotiateAgain = agent.negotiate(context);
		assertFalse(wantToNegotiateAgain);
		assertEquals(150.0, agent.computeUtility(context));
		assertEquals(
			"name: p $50.0 E50.0;",
			player.toString()
		);

		// critical params

		List<Object> criticalParams = new ArrayList<Object>();
		agent.addCriticalParams(criticalParams);
		assertEquals(1, criticalParams.size());
		assertEquals(1, criticalParams.get(0));
		BaseUtils.safeSetFieldVal(negotiation, criticalParams, "criticalParams");

		criticalParams.set(0, 0);
		wantToNegotiateAgain = agent.negotiate(context);
		assertFalse(wantToNegotiateAgain);
		assertEquals(145.0, agent.computeUtility(context));
		assertEquals(
			"name: p $45.0 E50.0;",
			player.toString()
		);

		// want all

		criticalParams.set(0, 1);
		wantToNegotiateAgain = agent.negotiate(context);
		assertTrue(wantToNegotiateAgain);
		wantToNegotiateAgain = agent.negotiate(context);
		assertFalse(wantToNegotiateAgain);
		assertEquals(300.0, agent.computeUtility(context));
		assertEquals(
			"name: p $100.0 E100.0;",
			player.toString()
		);
	}

	public void testCriticalParamsClear() {
		final StringBuilder sb = new StringBuilder();

		NegotiationForAbsEach<Integer> forConstr =
			new NegotiationForAbsEach<Integer>(Arrays.asList(1, 2));
		forConstr.getAgents().add(
			new BaseUtilityFuncAgent() {
				@Override
				public double computeUtility(INegotiationContext context) {
					return 0;
				}
				@SuppressWarnings("unchecked")
				@Override
				public boolean negotiate(INegotiationContext context) {
					IUtilityInfoService utilServ =
						context.getService(IUtilityInfoService.class);
					IExecuteForService<Integer> forServ =
						context.getService(IExecuteForService.class);
					sb.append("critical params size = ")
						.append(utilServ.getCriticalParams().size())
						.append(" item: ").append(forServ.getCurrentElem())
						.append("; ");
					return false;
				}
				@Override
				public void addCriticalParams(
					List<Object> criticalParams
				) {
					assertEquals(0, criticalParams.size());
					criticalParams.add(null);
					criticalParams.add(null);
					criticalParams.add(null);
					assertEquals(3, criticalParams.size());
					sb.append("addCriticalParams; ");
				}
			}
		);

		UtilityFuncNegotiation negotiation =
			new UtilityFuncNegotiation(
				ExecuteForNegotiation.newSeparateFor(forConstr)
			);
		negotiation.setNegotiateLimit(2);
		negotiation.negotiate();

		assertEquals(
			"critical params size = 0 item: 1; critical params size = 0 item: 2; addCriticalParams; " +
			"critical params size = 3 item: 1; critical params size = 3 item: 2; addCriticalParams; ",
			sb.toString()
		);
	}

	public void testUtilityNegAgentSelection() {
		final StringBuilder sb = new StringBuilder();

		NegotiationForAbsEach<Integer> forConstr =
			new NegotiationForAbsEach<Integer>(
				Arrays.asList(1, 2)
			);
		forConstr.getAgents().add(
			new BaseUtilityFuncAgent() {
				@Override
				public double computeUtility(INegotiationContext context) {
					return 0;
				}
				@SuppressWarnings("unchecked")
				@Override
				public boolean negotiate(INegotiationContext context) {
					IExecuteForService forServ =
						context.getService(IExecuteForService.class);
					sb.append("for-negotiate-item = ")
						.append(forServ.getCurrentElem())
						.append("; ");
					return false;
				}
			}
		);

		UtilityFuncNegotiation negotiation =
			new UtilityFuncNegotiation(
				ExecuteForNegotiation.newSeparateFor(forConstr)
			);
		negotiation.setNegotiateLimit(1);
		negotiation.negotiate();

		assertEquals(
			"for-negotiate-item = 1; for-negotiate-item = 2; ",
			sb.toString()
		);
	}

	public void testUtilityForNegotiationFlow() {
		final StringBuilder sb = new StringBuilder();

		FakeActivationCmd fakeCmd = new FakeActivationCmd(
			new FakeForCmd<Integer>(sb, "Acmd")
		);
		NegotiationForAbsEach<Integer> forConstr =
			new NegotiationForAbsEach<Integer>(
				Arrays.asList(1, 2),
				new FakeForCmd<Integer>(sb, "cmd"),
				fakeCmd
			);
		forConstr.getAgents().add(
			new BaseUtilityFuncAgent() {
				@Override
				public double computeUtility(
					INegotiationContext context
				) {
					return 0;
				}
				@SuppressWarnings("unchecked")
				@Override
				public boolean negotiate(INegotiationContext context) {
					IExecuteForService forServ =
						context.getService(IExecuteForService.class);
					sb.append("for-negotiate-item = ")
						.append(forServ.getCurrentElem())
						.append("; ");
					return false;
				}
			}
		);

		forConstr.getAgents().add(
			new FakeActivator(
				fakeCmd
			)
		);

		UtilityFuncNegotiation negotiation =
			new UtilityFuncNegotiation(
				ExecuteForNegotiation.newSeparateFor(forConstr)
			);
		negotiation.setNegotiateLimit(1);
		IExecutionContext context = negotiation.negotiate();
		negotiation.execute(context);

		assertEquals(
			/**
			 * it is not bug, it is a feature that
			 * in execution stage no agents activate Acmd
			 */
			"for-negotiate-item = 1; for-negotiate-item = 2; " +
			"cmd1Acmd1cmd2",
			sb.toString()
		);
	}

	public void test_onSingleNegotiationStageEnd_event() {
		final StringBuilder sb = new StringBuilder();

		NegotiationForAbsEach<Integer> forConstr =
			new NegotiationForAbsEach<Integer>(
				Arrays.asList(1, 2),
				new FakeForCmd<Integer>(sb, ";cmd1"),
				new FakeForCmd<Integer>(sb, ";cmd2")
			);
		forConstr.getAgents().add(
			new BaseUtilityFuncAgent() {
				@Override
				public double computeUtility(
					INegotiationContext context
				) {
					return Double.MAX_VALUE;
				}
				@SuppressWarnings("unchecked")
				@Override
				public boolean negotiate(INegotiationContext context) {
					IExecuteForService forServ =
						context.getService(IExecuteForService.class);
					sb.append("negotiate")
						.append(forServ.getCurrentElem())
						.append("; ");
					return false;
				}
				@Override
				public void onSingleNegotiationStageEnd(
					INegotiationContext context
				) {
					sb.append("onSingleNegotiationStageEnd");
				}
			}
		);

		UtilityFuncNegotiation negotiation =
			new UtilityFuncNegotiation(
				ExecuteForNegotiation.newSeparateFor(forConstr)
			);
		negotiation.setNegotiateLimit(3);
		IExecutionContext context = negotiation.negotiate();
		negotiation.execute(context);

		assertEquals(
			"negotiate1; negotiate2; onSingleNegotiationStageEnd" +
			"negotiate1; negotiate2; onSingleNegotiationStageEnd" +
			"negotiate1; negotiate2; onSingleNegotiationStageEnd" +
			";cmd11;cmd21;cmd12;cmd22",
			sb.toString()
		);
	}
}
